#include "dataStructures.h"

/**Global Lists**/

//Create list of intial peers
struct list listInitialPeers;

//Create list of names of local content
struct list listLocalFiles;

//Create list of outstanding requests
struct list outstandingRequestsList;

//Create the content directory and data cache lists
struct list contentDirectoryList;
struct list dataCacheList;


//Implements a linked list for the data cache and content
//directory based on a Least Recently Used algorithm
//Head of List: Least Recent Item
//Tail of List: Most Recent Item

/**
 * Pushes a new item onto a list and remove an item if the
 * list capacity has been reached.
 *
 * @param cacheItem - item to add to cacheable
 */
void push(struct list * itemList, struct node *item) {
	
	//Handles case if cache is empty
	if (itemList->size == 0) {
		
		//Sets item as the head and tail of the list and increments
		//the list size
		
		itemList->head = item;
		itemList->tail = item;
		itemList->size++;
		
	}
	
	//If cache is not full, then add item
	else if (itemList->size < itemList->maxSize) {
		
		//Point tail of list to newly added item
		itemList->tail->next = item;
		
		//Updates tail to point to newest item
		itemList->tail = item;
		itemList->size++;
		
	}
	
	//If the cache is full, then remove the oldest
	//item (head) and add the new item to the tail
	else {
		
		//pointer to hold original head location
		struct node * oldHead  = itemList->head;
		
		//Update head to the next in line while deleting head (freeing its memory)
		itemList->head = itemList->head->next;
		free(oldHead);
		
		//Point tail of list to newly added item
		itemList->tail->next = item;
		
		//Updates tail to point to newest item
		itemList->tail = item;
		
	}
	
	// Sets list tail to NULL
	itemList->tail->next = NULL;
}

/**
 * Search local file or dataCache or contentDirectory lists for a file
 *
 * @param list - list to search on
 * @param name - name of file to search for
 * @param flag - 0 for local file list search
 *               1 for dataCache list search
 *               3 for outstandingRequestsList list search
 * @return found - true if file is found in list; otherwise false
 */

int searchList(struct list * listToSearch, char * name, unsigned char flag) {
    
    // message buffer
    static char bufferMessage[MAX_MSG];
    
    // Boolean to say if item found or not
    unsigned char found = false;
    
    void * ptr;
    char * localFile;
    struct dataCache * tempDataCache;
    struct contentDir * tempContentDir;
    struct outstandingReq * tempOutstandingReq;
    
    struct node * it = createIterator(*listToSearch);
    
    // Temporary node for storing node previous to iterator
    // on calls to next
    struct node * tempNode;
    
    //Temp node to free memory
    struct node * trashNode = it;
    
    // Perform different search functions based on flag entered
    switch (flag){
			
			// Search local file list (nodes containing strings)
		case 0:
			
			// iterate through list and search for filename
			while (hasNext(it)){
				// Go to next node in list
				next(&it);
				
				// Cast node contents as a string         
				localFile = (char *) it->payload;
				
				// Compare strings
				if (strcmp(localFile, name) == 0){
					//Get data and attach it to data message to send
					
					
					found = true;
					
					//Debugging statement
					if (debug){
						printf("%s FOUND on local machine.\n", localFile);
					}
					
					break;
				}
				
				
			} // End while
			
			//Debug Statement
			if (debug){
				if (!found){
					printf("%s NOT found on local machine\n", name);
				}
			}
			break;
			
			// Search data cache list
		case 1:
			
			// iterate through data cache list and search for filename
			while (hasNext(it)){
				// Go to next node in list.  Store previous node
				// in case we need to update list.
				tempNode = next(&it);
				
				// Cast node contents as a dataCache struct         
				tempDataCache = (struct dataCache *) it->payload;
				
				// Compare strings
				if (strcmp(tempDataCache->name, name) == 0){
					
					//Get data and attach it to data message to send
					if (it == (listToSearch->head) ){
						
						//Assign new head
						listToSearch->head = it->next; 
						
						// Put the current node at the tail and update tail
						listToSearch->tail->next = it;
						listToSearch->tail = it;
						it->next = NULL;
					}
					
					//If node is at tail (most recently used) then we 
					//do nothing to the list.
					if (it != (listToSearch->tail)){
						// Skip over node iterator is pointing to
						tempNode->next = it->next;              
						
						// Put the current node at the tail and update tail
						listToSearch->tail->next = it;
						listToSearch->tail = it;
						it->next = NULL;
					}
					
					//Set found variable to true
					found = true;						
					
					//Debugging statement
					if (debug){
						printf("%s FOUND in data cache.\n", name);
					}
					
					break;
				}  
				
			} // End while
			
			// Debug statement
			if (debug){
				if (!found){
					printf("%s NOT found in data cache.\n", name);
				}
			}
			
			break;        
			
		case 3:
			
			// iterate through outstandingRequest list and search for filename        
			while (hasNext(it)){
				// Go to next node in list.  Store previous node
				// in case we need to update list.
				tempNode = next(&it);
				
				// Cast node contents as a dataCache struct         
				tempOutstandingReq = (struct outstandingReq *) it->payload;
				
				// Compare strings
				if (strcmp(tempOutstandingReq->name, name) == 0){
					
					//If head, then update head
					if (it == (listToSearch->head)){
						listToSearch->head = it->next;
					}
					
					//If node is not at the tail, then we go to the one previous
					//to it and link it to the one after it.
					if (it != (listToSearch->tail)){
						tempNode->next = it->next;              
					}
					
					// if node is at the tail, then we set previous to null.
					else {
						tempNode->next = NULL;
						listToSearch->tail = tempNode;
					}
					
					// Decrement the size of the list since we are removing
					// an outstanding request
					(listToSearch->size)--;
					
					//Set found variable to true
					found = true;
					
					//Debugging statement
					if (debug){
						printf("%s FOUND in outstanding request list.\n", name);
					}
					
					break;
				}       
				
			} // End while
			
			// Debug statement
			if (debug){
				if (!found){
					printf("%s NOT found in outstanding request list.\n", name);
				}
			}
			
			break;
			
		default:
			error("Flag must be 0, 1, or 3");
    }
    
    free(trashNode);
    return found;
} // End searchList

/**
 * Function that searches through the content directory for duplicates.
 * This function needs to be called prior to any adding of nodes to the
 * content Directory list.
 *
 * @param name = name of the file to search.
 * @param addressPort = sender's info : ip address and port.
 */
int searchContentDir(char * name, struct addressPort ap){
	
	short int flag = false;
	
	// Create iterator
	struct node * iter = createIterator(contentDirectoryList);
	struct contentDir * tempContentDir;
	
	//Debug message
	if (debug){
	    printf("searchContentDir function: Searching. . .\n");
	}
	
	// Iterate through the content drectory list
	while (hasNext(iter)) {
		next(&iter);
		tempContentDir = (struct contentDir *) (iter->payload);
		
		// Checks if it's unique
		if (strcmp(name,tempContentDir->name) == 0 && (ap.address == tempContentDir->address) && (ap.port == tempContentDir->port)){
			flag = true;
		}
		
	}
	
	// Debug message
	if (debug){
		if (flag == true) {
			printf("searchContentDir function: %s inside content directory!\n", name);
		}
		else {
			printf("searchContentDir function: %s not inside content directory!\n", name);
		}
	}
	
	return flag;
} // End searchContentDir

/**
 * Function that searches through the outstanding request list for a filename.
 * If a match on the name is found, and this is a user-originated request
 * this updates the timestamp of the request and also the number of times 
 * that the request has been retrieved and resent.
 *
 * If this is a request that was on the behalf of another peer then create
 * the content list on t
 *
 * @param name = name of the file to search.
 * @param addressPort = sender's info : ip address and port.
 * @return boolean - true if name is found, false if not
 */
int searchOutstandingReqList( char * name, char * message ){
	
	// Creates array of outstandingReq structs
	// to hold nodes until searching is over
	struct node * requestHold[3];
	int indexCount = 0;
	
	int expire = false; 
	
	// flag denotes whether name is in list	
	short int flag = false;
	struct node * previousNode; 
	
	// Create iterator
	struct node * iter = createIterator(outstandingRequestsList);
	// For garbage collection later
	struct node * trashIter = iter;
	// For retreiving information from node
	struct outstandingReq * tempOutstandingReq;
	
	// Iterate through outstanding request list
	while (hasNext(iter)) {
		previousNode = next(&iter);
		
		tempOutstandingReq = (struct outstandingReq *) (iter->payload);
		
		// If match found
		if (strcmp(name,tempOutstandingReq->name) == 0 ){
			
			//Set flag to true since we found a node containing name
			flag = true;
			
			// Test to see if it's a user request or not
			
			//Handles case where it's NOT a user request.  Removes from
			//list.
			if (tempOutstandingReq->originator){
				//Decrement size of list
				outstandingRequestsList.size--;
				
				//Handles case where node is at head
				if (iter == outstandingRequestsList.head){
					outstandingRequestsList.head = iter->next;
				}
				//If node at tail
				else if (iter == outstandingRequestsList.tail){
					outstandingRequestsList.tail = previousNode;
					previousNode->next = NULL;
				}
				//If node is in middle
				else {
					previousNode->next = iter->next;
				}        
			} //End if NOT user request
			
			// If it's a user generated request, update timestamp and count 
			// then move node to the back of the list.
			else {
				
				// Check if it's been requested 5 times or more and remove from the list.
				if (tempOutstandingReq->count >= 5){
					
					expire = true;
					// If it's the head
					if(iter == outstandingRequestsList.head){
						
						//Set the 2nd node as the new head
						outstandingRequestsList.head->next = iter->next ;
						outstandingRequestsList.head = iter->next;
						
					} // If it's the tail
					else if (iter == outstandingRequestsList.tail){
						// Make the previos node that tail and point its next to null
						previousNode->next = NULL;
						outstandingRequestsList.tail = previousNode;
						
					}
					// If it's in the middle
					else {
						
						//Set the head's next to the tail
						previousNode->next = iter->next;
					}
					
					printf("\nContent: %s\nNOT FOUND\n", tempOutstandingReq->name);
					
				}else if (expire == false) {
					
					//Store node in array
					requestHold[indexCount] = iter;
					indexCount++;
					
					
					// If it's the head
					if(iter == outstandingRequestsList.head){
						
						//Set the 2nd node as the new head
						outstandingRequestsList.head = iter->next ;
						//update timestamp
						tempOutstandingReq->timeCreated = time(NULL);
						//update count
						tempOutstandingReq->count++;          
					}
					
					// If it's the tail
					else if (iter == outstandingRequestsList.tail){
						//update timestamp
						tempOutstandingReq->timeCreated = time(NULL);
						//update count
						tempOutstandingReq->count++;	
					}
					
					// If it's in the middle
					else {
						
						//Set the head's next to the tail
						previousNode->next = iter->next;
						
						//update timestamp
						tempOutstandingReq->timeCreated = time(NULL);
						//update count
						tempOutstandingReq->count++;
						
					}			
				}
			}
		} // End if name matches node
		
	} // End while loop
	
	//If array has contents, then we attach all to end of list
	if (indexCount){
		int i;
		
		unsigned char filename[MAX_NAME];
		memset(filename, 0, sizeof(filename));
		int messageIndex = 0;
		
		
		
		// Determine the filename provided by the try message.
		strcpy((char *) filename, (char *) message+2);
		
		// Add 1 initially to account for the selector byte in the try message.
		messageIndex = 1 + strlen((char *) filename)+2;
		
		// Handle multi-byte peer count.
		// The amount of peers that have corresponding file provided in the try message.
		short int peerCount;
		short int * peerCountPtr;
		peerCountPtr = (short int *) &message[6];
		peerCount = ntohs(*peerCountPtr);
		
		// The peer count value is 2-bytes long so compute using two char elements of the message.
		messageIndex += 2;
		
		
		struct node * iterator = createIterator(outstandingRequestsList);
		struct node * freeIterator = iterator;
		
		unsigned long int address;
		unsigned long int * addressPtr;
		unsigned short int port;
		unsigned short int * portPtr;
		
		// Send request message to peers listed in the try message.
		for (i = 0; i < peerCount; i++) {
			
			
			// Parse 4 bytes of char for the peer ip address.
			addressPtr = (unsigned long int *) &message[messageIndex];
			address = ntohl(*addressPtr);
			messageIndex += 4;
			
			// Parse 2 bytes of char for the peer port number.
			portPtr = (unsigned short int *) &message[messageIndex];
			port = ntohs(*portPtr);
			messageIndex += 2;
			
			
			sendRequestMessage(filename,address, port); 
			
		}
		
		// Add each found node into the end of the outstanding request list
		for (i = 0; i < indexCount; i++){
			
			// Place the node in the tail and set next to null
			outstandingRequestsList.tail->next = requestHold[i];
			outstandingRequestsList.tail = requestHold[i];
			requestHold[i]->next = NULL;
		}
	}
	
	free (trashIter);
	
	return flag;
} // End of Search Outstanding Request List

/**
 * Retreives from the data cache list having the filename requested.
 * 
 * @param filename The filename of the file requested.
 * @return iterator A pointer to the node containing the data cache item in
 *									its payload.
 */
struct dataCache * getDataCacheItem(char * filename) {
	
	void * ptr;
	struct dataCache * tempDataCache;
	
	struct node * iterator = createIterator(dataCacheList);
	
	// Temporary node for storing node previous to iterator
	// on calls to next
	struct node * tempNode;
	
	//Temp node to free memory
	struct node * trashNode = iterator;
	
	// Iterate through data cache list and search for filename.
	while (hasNext(iterator)) {
		
		// Store previous node in case we need to update the list.
		tempNode = next(&iterator);
		
		// Cast node contents as a dataCache struct.
		tempDataCache = (struct dataCache *) iterator->payload;
		
		// If a match is found, sort the list to reflect LRU algorithm 
		// and return matching node.
		if (strcmp(tempDataCache->name, filename) == 0) {
			
			if (iterator == dataCacheList.head) {
				dataCacheList.head = iterator->next;
				dataCacheList.tail->next = iterator;
				dataCacheList.tail = iterator;
				iterator->next = NULL;
			}
			// Ignore the sorting process if the node is at the tail.
			else if (iterator != dataCacheList.tail) {
				tempNode->next = iterator->next;
				dataCacheList.tail->next = iterator;
				dataCacheList.tail = iterator;
				iterator->next = NULL;
			}
			
			free(trashNode);
			return tempDataCache;
		}
	}
	return NULL;
} // End createDataCacheItem

/**
 * Creates a new dataCache node
 *
 * @param name - name of file
 * @param content - file content (not a string)
 */
struct node * createCacheItem(char *name, char * content, int length){
	
	//Allocate space for new node;
	struct node * tempNode = (struct node *) malloc (sizeof(struct node));
	
	//Allocate space for new dataCache struct
	struct dataCache * tempItem = (struct dataCache * ) malloc ( sizeof(struct dataCache) );
	
	tempNode->payload = (void *) tempItem;
	
	//Copy in name and content
	strcpy(tempItem->name, name);
	//memcpy(tempItem->content, content, sizeof(content));
	memcpy(tempItem->content, content, length);
	tempItem->contentLength = length;
	
	return tempNode;
} // End createCacheItem

/**
 * Creates a new content directory node
 *
 * @param name - name of file
 * @param content - file content (not a string)
 */
struct node * createContentDirItem (char * name, struct addressPort senderInfo){
	//Allocate space for new node;
	struct node * tempNode = (struct node *) malloc (sizeof(struct node));
	
	// Allocate space for content directory item
	struct contentDir * tempItem = (struct contentDir *) malloc (  sizeof(struct contentDir) );
	
	//Assign payload for node to new item
	tempNode->payload = (void * ) tempItem;
	
	// copy in name and address/port
	strcpy(tempItem->name, name);
	tempItem->address = senderInfo.address;
	tempItem->port = senderInfo.port;
	
	return tempNode;
}

/**
 * Creates a new outstanding request node
 *
 * @param name - name of file\
 * @param originator - flag to denote who the request originated from
 *                     0 - user
 *                     1 - request on behalf of another peer
 */
struct node * createOutstandingReqItem (char * name, char originator){
	//Allocate space for new node;
	struct node * tempNode = (struct node *) malloc (sizeof(struct node));
	
	// Allocate space for content directory item
	struct outstandingReq * tempItem = (struct outstandingReq *) malloc (  sizeof(struct outstandingReq) );
	
	//Assign payload for node to new item
	tempNode->payload = (void * ) tempItem;
	
	// copy in name
	strcpy(tempItem->name, name);
	
	// get current time
	tempItem->timeCreated = time(NULL);
	
	// set originator flag 
	tempItem->originator = originator;
	
	// initialize count (number of requests) to 1
	tempItem->count = 1;
	
	//Prints the outstanding request node created
	if (debug) {
		printf("Outstanding Request\nFILENAME: %s\nTIME: %ld\n", tempItem->name, tempItem->timeCreated); 
	}
	
	return tempNode;
} // End createOutstandingReqItem

/**Iterator Functions**/

/**
 * Iterator hasNext() function tests if iterator is at the end of the list
 *
 * @param it - iterator
 * @return true if next node is not null, false if it is
 */
int hasNext(struct node * it) {
	// If the iterator is NULL (meaning the list size is 0)
	// Return false
	if (it == NULL){
		return false;
	}
	
	// See if iterator is pointing to a valid node
	if (it->next != NULL){
		return true;
	}
	
	else {
		return false;
	}
	
} // End hasNext

/**
 * Creates an iterator over a specified list
 *
 * @param listToIterate - a list to iterate over
 * @return pointer to the head of the list
 */

struct node * createIterator (struct list listToIterate) {
	
	//Create beforeHead node so that hasNext will return true
	//if there are items in the list  
	if (listToIterate.size > 0) {
		
		struct node * beforeHead = (struct node *) malloc (sizeof(struct node));
		
		//Set next node equal to head of list
		beforeHead->next = listToIterate.head;
		
		return beforeHead;
		
	}
	
	else {
		return NULL;
	}
} // End createIterator

/**
 * Moves the iterator to the next item in the list.
 *
 * @param it - iterator address
 * @return the current node
 */
struct node * next(struct node ** iteratorAddress) {
	
	struct node * tempNode = *iteratorAddress;
	*iteratorAddress = (*iteratorAddress)->next;
	return tempNode;
	
} // End next